package com.greate.community.filter;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import io.micrometer.core.instrument.MeterRegistry;
import lombok.extern.slf4j.Slf4j;
import lombok.var;
import org.springframework.core.Ordered;
import org.springframework.web.filter.OncePerRequestFilter;
import org.springframework.web.util.ContentCachingRequestWrapper;
import org.springframework.web.util.ContentCachingResponseWrapper;
import org.springframework.web.util.WebUtils;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.time.LocalDateTime;
import java.util.*;

@Slf4j
public class HttpTraceLogFilter extends OncePerRequestFilter implements Ordered {

    private static final String NEED_TRACE_PATH_PREFIX = "/";
    private static final String IGNORE_CONTENT_TYPE = "multipart/form-data";
    private static final ObjectMapper mapper = new ObjectMapper();

    private final MeterRegistry registry;

    public HttpTraceLogFilter(MeterRegistry registry) {
        this.registry = registry;
    }

    @Override
    public int getOrder() {
        return Ordered.LOWEST_PRECEDENCE - 10;
    }

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        if (!isRequestValid(request)) {
            filterChain.doFilter(request, response);
            return;
        }
        ContentCachingRequestWrapper requestWrapper = new ContentCachingRequestWrapper(request);
        ContentCachingResponseWrapper responseWrapper = new ContentCachingResponseWrapper(response);
        var startTime = LocalDateTime.now();
        try {
            filterChain.doFilter(requestWrapper, responseWrapper);
        } finally {
            String path = request.getRequestURI();
            if (path.startsWith(NEED_TRACE_PATH_PREFIX) && !Objects.equals(IGNORE_CONTENT_TYPE, request.getContentType())) {
                //创建一个 json 对象，用来存放 http 日志信息
                ObjectNode rootNode = mapper.createObjectNode();
                rootNode.put("uri", path);
                rootNode.put("clientIp", requestWrapper.getRemoteAddr());
                rootNode.put("startTime", startTime.toString());
                rootNode.put("endTime", LocalDateTime.now().toString());
                rootNode.set("requestHeaders", mapper.valueToTree(getRequestHeaders(requestWrapper)));
                String method = request.getMethod();
                rootNode.put("method", method);
                if ("GET".equals(method)) {
                    rootNode.set("request", mapper.valueToTree(requestWrapper.getParameterMap()));
                } else {
                    JsonNode newNode = mapper.readTree(requestWrapper.getContentAsByteArray());
                    rootNode.set("request", newNode);
                }
                rootNode.put("httpStatus", responseWrapper.getStatusCode());
                try {
                    JsonNode newNode = mapper.readTree(responseWrapper.getContentAsByteArray());
                    rootNode.set("response", newNode);
                } catch (Exception e) {
                    rootNode.put("response", new String(responseWrapper.getContentAsByteArray()));
                }
                responseWrapper.copyBodyToResponse();
                rootNode.set("responseHeaders", mapper.valueToTree(getResponseHeaders(responseWrapper)));
                log.info(rootNode.toString());
            }
        }
        updateResponse(responseWrapper);
    }

    private boolean isRequestValid(HttpServletRequest request) {
        try {
            new URI(request.getRequestURL().toString());
            return true;
        } catch (URISyntaxException ex) {
            return false;
        }
    }

    /**
     * 组装请求参数
     *
     * @param request
     * @return
     */
    private Map<String, Object> getRequestHeaders(HttpServletRequest request) {
        Map<String, Object> headers = new HashMap<>();
        Enumeration<String> headerNames = request.getHeaderNames();
        while (headerNames.hasMoreElements()) {
            String headerName = headerNames.nextElement();
            headers.put(headerName, request.getHeader(headerName));
        }
        return headers;

    }

    /**
     * 组装返回参数
     *
     * @param response
     * @return
     */
    private Map<String, Object> getResponseHeaders(ContentCachingResponseWrapper response) {
        Map<String, Object> headers = new HashMap<>();
        Collection<String> headerNames = response.getHeaderNames();
        for (String headerName : headerNames) {
            headers.put(headerName, response.getHeader(headerName));
        }
        return headers;
    }

    /**
     * 还原返回
     *
     * @param response
     * @throws IOException
     */
    private void updateResponse(HttpServletResponse response) throws IOException {
        ContentCachingResponseWrapper responseWrapper = WebUtils.getNativeResponse(response, ContentCachingResponseWrapper.class);
        Objects.requireNonNull(responseWrapper).copyBodyToResponse();
    }
}
